package com.xbongbong.saas.help.workflow;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.xbongbong.paas.constant.PaasConstant;
import com.xbongbong.paas.constant.TimeConstant;
import com.xbongbong.paas.domain.entity.PaasFormDataEntity;
import com.xbongbong.paas.enums.DelEnum;
import com.xbongbong.paas.toolbox.exception.XbbException;
import com.xbongbong.paas.toolbox.util.Arith;
import com.xbongbong.paas.toolbox.util.DateTimeUtil;
import com.xbongbong.paas.toolbox.util.StringUtil;
import com.xbongbong.pro.constant.BusinessConstant;
import com.xbongbong.pro.enums.ErrorCodeEnum;
import com.xbongbong.pro.enums.errorcodes.BusinessTypeErrorCodeEnum;
import com.xbongbong.saas.constant.ParameterConstant;
import com.xbongbong.saas.constant.StockConstant;
import com.xbongbong.saas.constant.StringConstant;
import com.xbongbong.saas.domain.entity.BatchFlowBillEntity;
import com.xbongbong.saas.domain.entity.BatchFlowBillUpdateEntity;
import com.xbongbong.saas.domain.entity.BatchSurplusRecordEntity;
import com.xbongbong.saas.enums.InstockTypeEnum;
import com.xbongbong.saas.enums.OutstockTypeEnum;
import com.xbongbong.saas.enums.SelectProductEnum;
import com.xbongbong.saas.enums.StockTypeEnum;
import com.xbongbong.saas.enums.XbbRefTypeEnum;
import com.xbongbong.saas.enums.business.ContractOutstockEnum;
import com.xbongbong.saas.enums.business.InstockEnum;
import com.xbongbong.saas.enums.business.OutstockEnum;
import com.xbongbong.saas.enums.business.ProductEnum;
import com.xbongbong.saas.enums.business.PurchaseInstockEnum;
import com.xbongbong.saas.enums.business.RefundInstockEnum;
import com.xbongbong.saas.enums.business.ReturnedPurchaseOutstockEnum;
import com.xbongbong.saas.enums.product.CostAdjustProductEnum;
import com.xbongbong.saas.enums.product.InstockProductEnum;
import com.xbongbong.saas.model.BatchFlowBillModel;
import com.xbongbong.saas.model.BatchSurplusRecordModel;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang.math.NumberUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.*;
import java.util.stream.Collectors;

/**
 *
 * @author 魏荣杰
 *
 */
@Component
@Slf4j
public class BatchFlowBillHelp {

    private static final Logger LOG = LoggerFactory.getLogger(BatchFlowBillHelp.class);
    @Resource
    private BatchFlowBillModel batchFlowBillModel;
    @Resource
    private BatchSurplusRecordModel batchSurplusRecordModel;


    /**
     * Description:
     * @param corpid
	 * @param businessType
	 * @param data
	 * @param paasFormDataEntity
     * @return void
     * @throws
     * @author 魏荣杰
     * @date 2021/7/23 13:42
     * @since
     */
    public void batchFlowModification4Save(String corpid, Integer businessType, JSONObject data, PaasFormDataEntity paasFormDataEntity) throws XbbException {
        JSONArray productArray = data.getJSONArray(OutstockEnum.PRODUCT.getAttr());
        Set<String> batchKeySet = new HashSet<>(PaasConstant.DEFAULT_INITIAL_CAPACITY);
        boolean isInStock = StockConstant.INSTOCK_TYPE_LIST.contains(businessType);
        boolean isOutStock = StockConstant.OUTSTOCK_TYPE_LIST.contains(businessType);
        boolean isOriStock = Objects.equals(businessType, XbbRefTypeEnum.ORI_STOCK.getCode());
        boolean isCostAdjust = Objects.equals(businessType, XbbRefTypeEnum.COST_ADJUST.getCode());
        if (isInStock) {
            Integer inStockType = data.getInteger(InstockEnum.TYPE.getAttr());
            businessType = InstockTypeEnum.getByCode(inStockType).getStockBusinessType();
        } else if (isOutStock) {
            Integer outStockType = data.getInteger(OutstockEnum.TYPE.getAttr());
            businessType = OutstockTypeEnum.getByCode(outStockType).getStockBusinessType();
        }
        Long refId = paasFormDataEntity.getId();
        Long dateTime = data.getLong(OutstockEnum.TIME.getAttr());
        if (Objects.isNull(dateTime)) {
            dateTime = DateTimeUtil.getTodayInt();
        }
        Map<String, BatchFlowBillUpdateEntity> batchFlowBillUpdateEntityMap = new HashMap<>(PaasConstant.DEFAULT_INITIAL_CAPACITY);
        // 合并相同批次纬度的数量，以便于批量更新
        for (int i = productArray.size() - 1; i > -1; i --) {
            JSONObject productObject = productArray.getJSONObject(i);
            String batch = Objects.toString(productObject.getString(SelectProductEnum.BATCH.getAttr()), "");
            if (StringUtil.isEmpty(batch)) {
                continue;
            }
            StringBuilder stringBuilder = new StringBuilder();
            Long pageProductId = productObject.getLong(SelectProductEnum.PRODUCT.getAttr());
            Long warehouseId;
            Object warehouseObj = productObject.get(SelectProductEnum.WAREHOUSE.getAttr());
            if (warehouseObj instanceof Long || warehouseObj instanceof Integer || NumberUtils.isNumber(String.valueOf(warehouseObj))) {
                warehouseId = productObject.getLong(SelectProductEnum.WAREHOUSE.getAttr());
            } else {
                JSONArray warehouseArray = productObject.getJSONArray(SelectProductEnum.WAREHOUSE.getAttr());
                JSONObject warehosueObj = warehouseArray.getJSONObject(0);
                warehouseId = warehosueObj.getLong("id");
            }
            Double num = productObject.getDouble(SelectProductEnum.NUM.getAttr());
            if (isOutStock) {
                num = Arith.sub(0D, num);
            }
            Double cost = productObject.getDouble(SelectProductEnum.COST.getAttr());
            Long guaranteePeriod =  Objects.isNull(productObject.getLong(InstockProductEnum.GUARANTEE_PERIOD.getAttr())) ? 0L : productObject.getLong(InstockProductEnum.GUARANTEE_PERIOD.getAttr()) * TimeConstant.SECONDS_PER_DAY;
            Long produceDate = Objects.isNull(productObject.getLong(InstockProductEnum.PRODUCE_DATE.getAttr())) ? 0L : productObject.getLong(InstockProductEnum.PRODUCE_DATE.getAttr());
            String batchKey = StringUtil.stringBuilderAppendSuffix(stringBuilder, StringConstant.CROSS, pageProductId, warehouseId, batch, produceDate, guaranteePeriod).toString();
            batchKeySet.add(batchKey);
            BatchFlowBillUpdateEntity batchFlowBillUpdateEntity = batchFlowBillUpdateEntityMap.get(batchKey);
            if (Objects.isNull(batchFlowBillUpdateEntity)) {
                batchFlowBillUpdateEntity = new BatchFlowBillUpdateEntity(batchKey, num, Arith.mul(num, cost), dateTime, DateTimeUtil.getInt(), refId);
            } else {
                batchFlowBillUpdateEntity.updateStock(num, cost);
            }
            batchFlowBillUpdateEntityMap.put(batchKey, batchFlowBillUpdateEntity);
        }
        if (batchKeySet.isEmpty()) {
            return;
        }
        try {
            Integer stockType;
            if (isInStock) {
                stockType = StockTypeEnum.IN_STOCK.getCode();
            } else if (isOutStock) {
                stockType = StockTypeEnum.OUT_STOCK.getCode();
            } else if (isOriStock) {
                stockType = StockTypeEnum.ORI_STOCK.getCode();
            } else if (isCostAdjust) {
                stockType = StockTypeEnum.ADJUST_STOCK.getCode();
            } else {
                throw new XbbException(BusinessTypeErrorCodeEnum.API_ERROR_361001);
            }
            // 查询最近的流水
            List<BatchFlowBillEntity> latestFlowBillList = batchFlowBillModel.getLatestFlowBillByBatchKey(corpid, batchKeySet, dateTime);
            // 当前插入数据的前一条流水记录
            Map<String, BatchFlowBillEntity> startTimeMap = new HashMap<>(PaasConstant.DEFAULT_INITIAL_CAPACITY);
            latestFlowBillList.forEach(item -> {
                startTimeMap.put(item.getBatchKey(), item);
                // 流水没有的用结存记录
                batchKeySet.remove(item.getBatchKey());
            });
            // 查询最近一次结存
            Map<String, BatchSurplusRecordEntity> startTimeRecordMap = new HashMap<>(PaasConstant.DEFAULT_INITIAL_CAPACITY);
            if (!batchKeySet.isEmpty()) {
                List<BatchSurplusRecordEntity> latestRecordList = batchSurplusRecordModel.getLatestFlowBillByBatchKey(corpid, batchKeySet, dateTime);
                latestRecordList.forEach(item -> {
                    startTimeRecordMap.put(item.getBatchKey(), item);
                });
            }
            List<BatchFlowBillEntity> addList = new ArrayList<>();
            String serialNo = paasFormDataEntity.getSerialNo();
            Long businessId = null;
            String businessName = null;
            if (Objects.equals(businessType, XbbRefTypeEnum.CONTRACT_OUTSTOCK.getCode())) {
                businessId = data.getLong(ContractOutstockEnum.LINK_CUSTOMER.getAttr());
                businessName = data.getString(ContractOutstockEnum.LINK_CUSTOMER_HIDE.getAttr());
            } else if (Objects.equals(businessType, XbbRefTypeEnum.REFUND_INSTOCK.getCode())) {
                businessId = data.getLong(RefundInstockEnum.CUSTOMER_ID.getAttr());
                businessName = data.getString(RefundInstockEnum.CUSTOMER_ID_LINKED_TEXT.getAttr());
            } else if (Objects.equals(businessType, XbbRefTypeEnum.PURCHASE_INSTOCK.getCode())) {
                businessId = data.getLong(PurchaseInstockEnum.SUPPLIER_ID.getAttr());
                businessName = data.getString(PurchaseInstockEnum.SUPPLIER_ID_LINKED_TEXT.getAttr());
            } else if (Objects.equals(businessType, XbbRefTypeEnum.RETURNED_PURCHASE_OUTSTOCK.getCode())) {
                businessId = data.getLong(ReturnedPurchaseOutstockEnum.LINK_SUPPLIER.getAttr());
                businessName = data.getString(ReturnedPurchaseOutstockEnum.LINK_SUPPLIER_HIDE.getAttr());
            }
            for (int i = 0; i < productArray.size(); i++) {
                StringBuilder stringBuilder = new StringBuilder();
                JSONObject productObject = productArray.getJSONObject(i);
                Long pageProductId = productObject.getLong(SelectProductEnum.PRODUCT.getAttr());
                Long parentId = productObject.getLong(ProductEnum.PARENT_ID.getSaasAttr());
                Long warehouseId;
                Object warehouseObj = productObject.get(SelectProductEnum.WAREHOUSE.getAttr());
                if (warehouseObj instanceof Long || warehouseObj instanceof Integer || NumberUtils.isNumber(String.valueOf(warehouseObj))) {
                    warehouseId = productObject.getLong(SelectProductEnum.WAREHOUSE.getAttr());
                } else {
                    JSONArray warehouseArray = productObject.getJSONArray(SelectProductEnum.WAREHOUSE.getAttr());
                    JSONObject warehosueObj = warehouseArray.getJSONObject(0);
                    warehouseId = warehosueObj.getLong("id");
                }
                Long productSubId = productObject.getLong(BusinessConstant.PRODUCT_SUB_ID);
                Double num = productObject.getDouble(SelectProductEnum.NUM.getAttr());
                Double cost = productObject.getDouble(SelectProductEnum.COST.getAttr());
                String memo = productObject.getString(SelectProductEnum.MEMO.getAttr());
                String batch = productObject.getString(SelectProductEnum.BATCH.getAttr());
                if (StringUtil.isEmpty(batch)) {
                    continue;
                }
                Long guaranteePeriod =  Objects.isNull(productObject.getLong(InstockProductEnum.GUARANTEE_PERIOD.getAttr())) ? 0L : productObject.getLong(InstockProductEnum.GUARANTEE_PERIOD.getAttr()) * TimeConstant.SECONDS_PER_DAY;
                Long produceDate = Objects.isNull(productObject.getLong(InstockProductEnum.PRODUCE_DATE.getAttr())) ? 0L : productObject.getLong(InstockProductEnum.PRODUCE_DATE.getAttr());
                String key = StringUtil.stringBuilderAppendSuffix(stringBuilder, StringConstant.CROSS, pageProductId, warehouseId, batch, produceDate, guaranteePeriod).toString();
                // 1.流水没有，结存有 2.流水没有，结存也没有 3.有流水
                if (!startTimeMap.containsKey(key)) {
                    if (startTimeRecordMap.containsKey(key)) {
                        Double remainingStock = num;
                        double remainingCost = Arith.mul(num, cost);
                        BatchSurplusRecordEntity batchSurplusRecordEntity = startTimeRecordMap.get(key);
                        if (isInStock || isOriStock) {
                            remainingStock = Arith.add(batchSurplusRecordEntity.getRemainingStock(), remainingStock);
                            remainingCost = Arith.add(batchSurplusRecordEntity.getRemainingCost(), remainingCost);
                        } else if (isCostAdjust) {
                            cost = productObject.getDouble(BusinessConstant.ADJUST_AMOUNT);
                            remainingStock = num;
                            remainingCost = cost;
                        } else {
                            remainingStock = Arith.sub(batchSurplusRecordEntity.getRemainingStock(), remainingStock);
                            remainingCost = Arith.sub(batchSurplusRecordEntity.getRemainingCost(), remainingCost);
                        }
                        BatchFlowBillEntity batchFlowBillEntity = new BatchFlowBillEntity(corpid, parentId, pageProductId, warehouseId, batch, produceDate, guaranteePeriod, produceDate + guaranteePeriod, businessId, businessName, num, cost, remainingStock, remainingCost, dateTime, businessType, key, memo, productSubId, stockType, serialNo, refId);
                        startTimeMap.put(key, batchFlowBillEntity);
                        addList.add(batchFlowBillEntity);
                    } else {
                        Double remainingStock = num;
                        double remainingCost = Arith.mul(num, cost);
                        if (isOutStock) {
                            remainingStock = Arith.sub(0D, remainingStock);
                            remainingCost = Arith.sub(0D, remainingCost);
                        } else if (isCostAdjust) {
                            cost = productObject.getDouble(BusinessConstant.ADJUST_AMOUNT);
                            remainingStock = num;
                            remainingCost = cost;
                        }
                        BatchFlowBillEntity stockFlowBillEntity = new BatchFlowBillEntity(corpid,parentId, pageProductId, warehouseId, batch, produceDate, guaranteePeriod, produceDate + guaranteePeriod, businessId, businessName, num, cost, remainingStock, remainingCost, dateTime, businessType, key, memo, productSubId, stockType, serialNo, refId);
                        startTimeMap.put(key, stockFlowBillEntity);
                        addList.add(stockFlowBillEntity);
                    }
                } else {
                    Double remainingStock = num;
                    double remainingCost = Arith.mul(num, cost);
                    BatchFlowBillEntity oldBatchFlowBillEntity = startTimeMap.get(key);
                    if (isInStock || isOriStock) {
                        remainingStock = Arith.add(oldBatchFlowBillEntity.getRemainingStock(), remainingStock);
                        remainingCost = Arith.add(oldBatchFlowBillEntity.getRemainingCost(), remainingCost);
                    } else if (isCostAdjust) {
                        Double oldCost = oldBatchFlowBillEntity.getRemainingCost();
                        Double adjustAmount = productObject.getDouble(BusinessConstant.ADJUST_AMOUNT);
                        cost = productObject.getDouble(BusinessConstant.ADJUST_AMOUNT);
                        remainingStock = oldBatchFlowBillEntity.getRemainingStock();
                        remainingCost =Arith.add(oldCost, adjustAmount);
                    } else {
                        remainingStock = Arith.sub(oldBatchFlowBillEntity.getRemainingStock(), remainingStock);
                        remainingCost = Arith.sub(oldBatchFlowBillEntity.getRemainingCost(), remainingCost);
                    }
                    BatchFlowBillEntity stockFlowBillEntity = new BatchFlowBillEntity(corpid, parentId, pageProductId, warehouseId, batch, produceDate, guaranteePeriod, produceDate + guaranteePeriod, businessId, businessName, num, cost, remainingStock, remainingCost, dateTime, businessType, key, memo, productSubId, stockType, serialNo, refId);
                    startTimeMap.put(key, stockFlowBillEntity);
                    addList.add(stockFlowBillEntity);
                }
            }
            // 先插入
            if (!addList.isEmpty()) {
                batchFlowBillModel.insertBatch(addList);
            }
            // 更新数据
            batchFlowBillModel.updateGreaterThanTime(batchFlowBillUpdateEntityMap.values(), corpid, batchFlowBillUpdateEntityMap.keySet());
        } catch (Exception e) {
            log.warn(String.format(StringConstant.BATCH_FLOW_BILL_MSG, corpid, businessType, refId),e);
        }
    }

    public void batchFlowModification4Delete(List<Long> deleteList, List<BatchFlowBillUpdateEntity> batchFlowBillUpdateEntityList, Integer businessType, String corpid)throws XbbException{
        if (CollectionUtils.isEmpty(batchFlowBillUpdateEntityList)) {
            return;
        }
        try {
            List<Integer> businessTypeIn;
            if (Objects.equals(businessType, XbbRefTypeEnum.INSTOCK.getCode())) {
                businessTypeIn = StockConstant.INSTOCK_TYPE_LIST;
            } else if (Objects.equals(businessType, XbbRefTypeEnum.OUTSTOCK.getCode())){
                businessTypeIn = StockConstant.OUTSTOCK_TYPE_LIST;
            } else if (Objects.equals(businessType, XbbRefTypeEnum.COST_ADJUST.getCode())){
                businessTypeIn = Collections.singletonList(XbbRefTypeEnum.COST_ADJUST.getCode());
            } else {
                businessTypeIn = Collections.singletonList(XbbRefTypeEnum.ORI_STOCK.getCode());
            }
            Map<String, Object> params = new HashMap<>(PaasConstant.DEFAULT_INITIAL_CAPACITY);
            params.put(ParameterConstant.CORPID, corpid);
            params.put(ParameterConstant.DEL, DelEnum.NORMAL.getDel());
            params.put("refIdIn", deleteList);
            params.put("businessTypeIn", businessTypeIn);
            List<BatchFlowBillEntity> batchFlowBillEntityList = batchFlowBillModel.findEntitys(params);
            List<Long> hasBatchFlow = new ArrayList<>();
            Map<String, List<Long>> batchKeyDateMap = new HashMap<>(batchFlowBillEntityList.size());
            batchFlowBillEntityList.forEach(item -> {
                String batchKeyDate = item.getBatchKey() + "_" + item.getDate();
                List<Long> idList = batchKeyDateMap.getOrDefault(batchKeyDate, new ArrayList<>());
                idList.add(item.getId());
                batchKeyDateMap.put(batchKeyDate, idList);
                hasBatchFlow.add(item.getRefId());
            });
            batchKeyDateMap.values().forEach(item -> item.sort(Long::compareTo));
            batchFlowBillModel.deleteByRefIdIn(corpid, deleteList, businessTypeIn);
            // 有可能删除了没有流水记录的单据，说明是上线前的数据，这种情况应该是更新当前batchKey下的所有流水记录
            Map<String, BatchFlowBillUpdateEntity> haveNoStockBillMap = new HashMap<>();
            Iterator<BatchFlowBillUpdateEntity> iterator = batchFlowBillUpdateEntityList.iterator();
            while (iterator.hasNext()) {
                BatchFlowBillUpdateEntity batchFlowBillUpdateEntity = iterator.next();
                if (!hasBatchFlow.contains(batchFlowBillUpdateEntity.getRefId())) {
                    if (haveNoStockBillMap.containsKey(batchFlowBillUpdateEntity.getBatchKey())) {
                        BatchFlowBillUpdateEntity stockFlowBillUpdate = haveNoStockBillMap.get(batchFlowBillUpdateEntity.getBatchKey());
                        stockFlowBillUpdate.setCost(Arith.add(stockFlowBillUpdate.getCost(), batchFlowBillUpdateEntity.getCost()));
                        stockFlowBillUpdate.setStock(Arith.add(stockFlowBillUpdate.getStock(), batchFlowBillUpdateEntity.getStock()));
                    } else {
                        haveNoStockBillMap.put(batchFlowBillUpdateEntity.getBatchKey(), batchFlowBillUpdateEntity);
                    }
                    iterator.remove();
                }
            }
            if (!haveNoStockBillMap.isEmpty()) {
                batchFlowBillModel.updateBatch(haveNoStockBillMap.values(), corpid, haveNoStockBillMap.keySet());
            }
            // 剩余有流水记录的数据删除 产品按时间升序排序
            List<BatchFlowBillUpdateEntity> sortedUpdateList = batchFlowBillUpdateEntityList.stream().sorted(Comparator.comparing(BatchFlowBillUpdateEntity::getStartTime)).collect(Collectors.toList());
            Map<String, List<BatchFlowBillUpdateEntity>> batchKeySortedMap = new HashMap<>(sortedUpdateList.size());
            sortedUpdateList.forEach(item -> {
                String batchKeyDate = item.getBatchKey() + "_" + item.getStartTime();
                Long startId = batchKeyDateMap.get(batchKeyDate).get(0);
                item.setStartId(startId);
                batchKeyDateMap.get(batchKeyDate).remove(0);
                List<BatchFlowBillUpdateEntity> updateList = batchKeySortedMap.getOrDefault(item.getBatchKey(), new ArrayList<>());
                if (!updateList.isEmpty()) {
                    BatchFlowBillUpdateEntity lastBatchFlowBillUpdateEntity = updateList.get(updateList.size() - 1);
                    lastBatchFlowBillUpdateEntity.setEndTime(item.getStartTime());
                    lastBatchFlowBillUpdateEntity.setEndId(startId);
                    item.setStock(Arith.add(lastBatchFlowBillUpdateEntity.getStock(), item.getStock()));
                    item.setCost(Arith.add(lastBatchFlowBillUpdateEntity.getCost(), item.getCost()));
                }
                updateList.add(item);
                batchKeySortedMap.put(item.getBatchKey(), updateList);
            });
            if (!batchKeySortedMap.isEmpty()) {
                batchFlowBillModel.updateGreaterThanTime(sortedUpdateList, corpid, batchKeySortedMap.keySet());
            }
        } catch (Exception e) {
            String refId = JSON.toJSONString(deleteList);
            LOG.warn(String.format(StringConstant.BATCH_FLOW_BILL_MSG, corpid, businessType, refId),e);
        }
    }
}
